﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Security.Authentication;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using IdentityModel;
using IdentityServer4.Stores;
using K9Nano.Admin.Data;
using K9Nano.Admin.Weixin.Dto;
using K9Nano.Application;
using K9Nano.Authentication;
using K9Nano.Authorization;
using K9Nano.Domain;
using K9Nano.Runtime;
using K9Nano.Share;
using K9Nano.Share.Exceptions;
using K9Nano.Web.Accounts;
using K9Nano.Web.Accounts.Dto;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using IMapper = AutoMapper.IMapper;
using ISystemClock = K9Nano.Runtime.ISystemClock;

namespace K9Nano.Admin.Weixin
{
    public abstract class WeixinAppService<TUser> : K9ApplicationService, IWeixinAppService where TUser : User, new()
    {
        protected readonly UserManager<TUser> UserManager;
        protected readonly ISystemClock SystemClock;
        protected readonly IClientStore ClientStore;
        protected readonly IUserBlacklistChecker BlacklistChecker;
        protected readonly IAccountAppService AccountAppService;

        protected WeixinAppService(IUnitOfWork uow,
            IMapper mapper,
            ISessionContext sessionContext,
            ILoggerFactory loggerFactory,
            UserManager<TUser> userManager,
            ISystemClock systemClock,
            IClientStore clientStore,
            IUserBlacklistChecker blacklistChecker,
            IAccountAppService accountAppService) : base(uow, mapper, sessionContext, loggerFactory)
        {
            UserManager = userManager;
            SystemClock = systemClock;
            ClientStore = clientStore;
            BlacklistChecker = blacklistChecker;
            AccountAppService = accountAppService;
        }

        protected virtual async Task<UserWeixinLogin> FindUserWeixinLoginAsync(WeixinMpLoginModel model,
           CancellationToken cancellation)
        {
            var weixinLoginRepository = Uow.Repository<UserWeixinLogin>();
            Expression<Func<UserWeixinLogin, bool>> predicate;
            if (string.IsNullOrEmpty(model.UnionId))
            {
                predicate = u => u.ClientId == model.ClientId
                                 && u.AppId == model.AppId
                                 && u.OpenId == model.OpenId;
            }
            else
            {
                predicate = u => u.ClientId == model.ClientId
                                 && u.AppId == model.AppId
                                 && u.UnionId == model.UnionId
                                 && u.OpenId == model.OpenId;
            }
            var login = await weixinLoginRepository.FirstOrDefaultAsync(predicate, cancellation);
            return login;
        }

        protected virtual async Task<AuthenticationTicket> BuildWeixinAuthenticationTicketAsync(Guid userId)
        {
            var user = await UserManager.FindByIdAsync(userId.ToString());

            if (user == null) throw new AuthenticationException("用户不存在：" + userId);

            return await BuildAuthenticationTicketAsync(user);
        }

        protected virtual async Task<AuthenticationTicket> BuildAuthenticationTicketAsync(TUser user)
        {
            var claims = await BuildUserClaimsAsync(user);

            return new AuthenticationTicket(new ClaimsPrincipal(new ClaimsIdentity(claims, K9SharedConstants.GrantTypeWeixinMP)), K9SharedConstants.GrantTypeWeixinMP);
        }

        protected virtual async Task<IList<Claim>> BuildUserClaimsAsync(TUser user)
        {
            var claims = await UserManager.GetClaimsAsync(user);
            claims.Add(new Claim(JwtClaimTypes.Subject, user.Id.ToString()));
            claims.Add(new Claim(K9SharedConstants.TenantIdClaimType, user.TenantId?.ToString() ?? String.Empty));
            //claims.Add(new Claim(K9SharedConstants.UserIdClaimType, user.UserName));
            claims.Add(new Claim(JwtClaimTypes.PreferredUserName, user.UserName));


            claims.Add(new Claim(JwtClaimTypes.IdentityProvider, "local")); // TODO
            claims.Add(new Claim(JwtClaimTypes.AuthenticationMethod, "weixin"));
            claims.Add(new Claim(JwtClaimTypes.AuthenticationTime, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString()));
            // TODO StateHash
            // claims.Add(new Claim(JwtClaimTypes.StateHash, "")); 

            //if (request.State.IsPresent())
            //{
            //    var credential = await KeyMaterialService.GetSigningCredentialsAsync();
            //    if (credential == null)
            //    {
            //        throw new InvalidOperationException("No signing credential is configured.");
            //    }

            //    var algorithm = credential.Algorithm;
            //    stateHash = CryptoHelper.CreateHashClaimValue(request.State, algorithm);
            // }

            return claims;
        }


        /// <summary>
        /// 获取绑定的用户
        /// </summary>
        /// <param name="model"></param>
        /// <param name="cancellation"></param>
        /// <returns>成功返回用户凭证，否则返回null</returns>
        public virtual async Task<AuthenticationTicket> WeixinAuthenticateAsync(WeixinMpLoginModel model, CancellationToken cancellation)
        {
            var login = await FindUserWeixinLoginAsync(model, cancellation);

            if (login == null)
            {
                return null;
            }

            if (await BlacklistChecker.IsForbiddenAsync(login.UserId.ToString(), model.ClientId, cancellation))
            {
                throw new K9AuthorizationException($"User({login.UserId}) is on the blacklist");
            }

            var ticket = await BuildWeixinAuthenticationTicketAsync(login.UserId);

            return ticket;
        }

        /// <summary>
        /// 绑定的用户
        /// </summary>
        /// <param name="model"></param>
        /// <param name="cancellation"></param>
        public virtual async Task WeixinMpBindAsync(WeixinMpBindModel model, CancellationToken cancellation)
        {
            var login = await FindUserWeixinLoginAsync(model, cancellation);

            if (login != null)
            {
                throw new K9UserFriendlyException("当前微信已经绑定了其他用户");
            }

            var userRepository = Uow.Repository<TUser, Guid>();

            var user = await userRepository.FirstOrDefaultAsync(x => x.PhoneNumber == model.Phone, cancellation);

            await Uow.RunTransactionAsync(async () =>
            {
                Guid userId;
                if (user == null)
                {
                    // 用户不存在，直接创建
                    userId = await AccountAppService.CreateAsync(new UserCreateInput
                    {
                        ClientId = model.ClientId,
                        Name = model.Phone,
                        Phone = model.Phone,
                        Avatar = model.Profile
                    }, cancellation);
                }
                else
                {
                    userId = user.Id;
                    if (!user.PhoneNumberConfirmed)
                    {
                        user.PhoneNumberConfirmed = true;
                        await userRepository.UpdateAsync(user, cancellation);
                    }
                }

                var weixinLoginRepository = Uow.Repository<UserWeixinLogin>();

                login = new UserWeixinLogin
                {
                    ClientId = model.ClientId,
                    AppId = model.AppId,
                    OpenId = model.OpenId,
                    UnionId = model.UnionId,
                    UserId = userId,
                    CreationTime = SystemClock.UtcNow
                };

                await weixinLoginRepository.InsertAsync(login, cancellation);
            });

            //var claims = await BuildUserClaimsAsync(user);
            //var token = await IdentityServerTools.IssueJwtAsync(3600, claims);
            //return token;
        }

        public async Task WeixinUnbindAsync(WeixinMpUnbindModel model, CancellationToken cancellation)
        {
            var weixinLoginRepository = Uow.Repository<UserWeixinLogin>();
            var gid = Guid.Parse(model.UserId);
            await weixinLoginRepository.DeleteAsync(x => x.ClientId == model.ClientId && x.UserId == gid,
                false,
                cancellation: cancellation);
        }

        /// <summary>
        /// 获取客户端回调地址
        /// </summary>
        /// <param name="clientId"></param>
        /// <returns></returns>
        public virtual async ValueTask<IdentityServer4.Models.Client> GetClientAsync(string clientId, CancellationToken cancellation)
        {
            var client = await ClientStore.FindEnabledClientByIdAsync(clientId);
            return client;
        }

        /// <summary>
        /// 校验客户端认证的回调地址是否合法
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="redirectUrl"></param>
        /// <returns></returns>
        public virtual async ValueTask<bool> ValidateRedirectUrlAsync(string clientId, string redirectUrl)
        {
            var client = await ClientStore.FindEnabledClientByIdAsync(clientId);
            if (client == null) return false;
            var path = QueryHelpers.GetHost(redirectUrl);
            return client.RedirectUris.Any(x => x.StartsWith(path));
        }

        /// <summary>
        /// 是否已经绑定
        /// </summary>
        /// <param name="clientId"></param>
        /// <param name="appId"></param>
        /// <param name="openId"></param>
        /// <param name="unionId"></param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public virtual async ValueTask<bool> IsBoundAsync(string clientId, string appId, string openId, string unionId,
            CancellationToken cancellation)
        {
            var weixinLoginRepository = Uow.Repository<UserWeixinLogin>();
            Expression<Func<UserWeixinLogin, bool>> predicate;
            if (string.IsNullOrEmpty(unionId))
            {
                predicate = u => u.ClientId == clientId
                                 && u.AppId == appId
                                 && u.OpenId == openId;
            }
            else
            {
                predicate = u => u.ClientId ==clientId
                                 && u.AppId == appId
                                 && u.UnionId == unionId
                                 && u.OpenId == openId;
            }
            var login = await weixinLoginRepository.AnyAsync(predicate, cancellation);
            return login;
        }
    }
}